/*
 * Copyright 2012 Marsrutai.lt
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package lt.marsrutai.mobile.android.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import lt.marsrutai.mobile.android.model.enums.WeekdayType;
import android.os.Parcel;
import android.os.Parcelable;

/**
 * <p>
 * Represents a set of {@link WeekdayType}s.
 * <p>
 * <p>
 * When two {@link DayGroup}s are compared with each other, only their first
 * days are compared. E.g. Day group of {1, 4} is less than day group of {2, 3}.
 * </p>
 * 
 * @author jvalskis
 */
public class DayGroup implements Iterable<WeekdayType>, Comparable<DayGroup>, Parcelable {

    public static final Parcelable.Creator<DayGroup> CREATOR = new Parcelable.Creator<DayGroup>() {

        @Override
        public DayGroup createFromParcel(Parcel source) {
            return new DayGroup(source);
        }

        @Override
        public DayGroup[] newArray(int size) {
            return new DayGroup[size];
        }
    };
    
    private SortedSet<WeekdayType> days = new TreeSet<WeekdayType>();

    public DayGroup(List<Integer> days) {
        if (days == null || days.isEmpty()) {
            throw new IllegalArgumentException("Day group must consist of at least one day.");
        }
        
        for (Integer day : days) {
            this.days.add(WeekdayType.parseEnumSunSat(day));
        }
    }

    public DayGroup(Set<WeekdayType> days) {
        if (days == null || days.isEmpty()) {
            throw new IllegalArgumentException("Day group must consist of at least one day.");
        }
        
        this.days.addAll(days);
    }
    
    public DayGroup(Parcel in) {
         List<WeekdayType> list = new ArrayList<WeekdayType>();
         in.readList(list, WeekdayType.class.getClassLoader());
         days.addAll(list);
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeList(new ArrayList<WeekdayType>(days));
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public int compareTo(DayGroup another) {
        return iterator().next().compareTo(another.iterator().next());
    }

    public boolean contains(Object object) {
        return days.contains(object);
    }

    public boolean containsAll(Collection<?> arg0) {
        return days.containsAll(arg0);
    }

    public int size() {
        return days.size();
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((days == null) ? 0 : days.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        DayGroup other = (DayGroup) obj;
        if (days == null) {
            if (other.days != null) {
                return false;
            }
        }
        else if (!days.equals(other.days)) {
            return false;
        }
        return true;
    }

    @Override
    public Iterator<WeekdayType> iterator() {
        return days.iterator();
    }
    
    /**
     * <p>Returns all consecutive days split into separate parts.</p>
     * <pre>
     * {monday}                              -> {{monday}}
     * {monday, tuesday}                     -> {{monday, tuesday}}
     * {monday, thursday}                    -> {{monday}, {thursday}}
     * {monday, tuesday, thursday, saturday} -> {{monday, tuesday}, {thursday}, {saturday}}
     * </pre> 
     * 
     * @return A list of separate consecutive day "islands" in this day group
     */
    public List<SortedSet<WeekdayType>> getConsecutiveDays() {
        List<SortedSet<WeekdayType>> list = new ArrayList<SortedSet<WeekdayType>>();
        
        SortedSet<WeekdayType> consecutiveDays = new TreeSet<WeekdayType>();
        
        WeekdayType previous = null;
        Iterator<WeekdayType> iterator = iterator();
        if (iterator.hasNext()) {
            previous = iterator.next();
            consecutiveDays.add(previous);
        }

        while (iterator.hasNext()) {
            WeekdayType day = iterator.next();

            if (previous != null && previous.ordinal() + 1 != day.ordinal()) {
                /*
                 * Store consecutive day set when current day is not the day
                 * after the previous day
                 */
                list.add(consecutiveDays);
                consecutiveDays = new TreeSet<WeekdayType>();
            }

            consecutiveDays.add(day);
            previous = day;
        }
        
        // Store consecutive day set
        if (!consecutiveDays.isEmpty()) {
            list.add(consecutiveDays);
        }
        
        return list;
    }
}
